'''
Type class. A type converstion utility used to convert text strings into native
python types.
Copyright (C) 2006  Jonathan Claggett

This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free
Software Foundation; either version 2.1 of the License, or (at your option) any
later version.

This library is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
details.

You should have received a copy of the GNU Lesser General Public License along
with this library; if not, write to the Free Software Foundation, Inc., 51
Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

You may contact the author at <jonathan@claggett.org>
'''

import re

# Type flags
RAW = 0
INT = INTEGER = 1
HEX = HEXADECIMAL = 2
FLOAT = 4
PERCENT = 8
BOOL = BOOLEAN = 16
STR = STRING = 32
NUMBER = INTEGER | HEXADECIMAL | FLOAT | PERCENT
ANY = NUMBER | BOOLEAN | STRING

# Type Casting Exceptions
class TypeCastError(Exception):
    pass

def cast( value, types=ANY ):
    '''
    The cast function is used to convert string values into into native data
    types. For example: x="123.3" => x=123.3.
    '''

    if types == RAW:
        return value

    # Build the pattern
    type_str = r'^(?:'
    if types & INTEGER:
        type_str += r"(?P<int> [-+]?\d+ ) | "
    if types & HEXADECIMAL:
        type_str += r"(?P<hex> 0x[\da-f]+ ) | "
    if types & FLOAT:
        type_str += r"(?P<float> [-+]?(\d+(\.\d*)?|\d*\.\d+)(e[-+]?\d+)? ) | "
    if types & PERCENT:
        type_str += r"(?P<percent> [-+]?(\d+(\.\d*)?|\d*\.\d+)(e[-+]?\d+)? ) % | "
    if types & BOOLEAN:
        type_str += r"(?P<bool> (true|false) ) | "
    if types & STRING:
        type_str += r"'?  (?P<str>  .*? ) '? | "
    type_str += r'. )$'

    type_pattern = re.compile(type_str,
        re.DOTALL | re.IGNORECASE | re.UNICODE | re.VERBOSE )

    assert type(value) in (str, unicode)
    match = type_pattern.search(value).groupdict()

    if types & INTEGER and match['int'] != None:
        return int(match['int'])
    elif types & HEXADECIMAL and match['hex'] != None:
        return int(match['hex'], 16)
    elif types & FLOAT and match['float'] != None:
        return float(match['float'])
    elif types & PERCENT and match['percent'] != None:
        return float(match['percent']) / 100.0
    elif types & BOOLEAN and match['bool'] != None:
        return match['bool'].lower() == 'true'
    elif types & STRING and match['str'] != None:
        return match['str']
    else:
        raise TypeCastError, "Unable to convert string to specified types."

# Unit Testing
if __debug__:
    from unittest import TestCase

    class TestCast(TestCase):
        def test_casting_numbers(self):
            self.assertEqual(100, cast('100'))
            self.assertEqual(255, cast('0xFF'))
            self.assertEqual(1.5, cast('1.5'))
            self.assertEqual(0.5, cast('50%'))

        def test_casting_booleans(self):
            self.assertEqual(True, cast('true'))
            self.assertEqual(False, cast('false'))

        def test_casting_strings(self):
            self.assertEqual('hello', cast("hello"))
            self.assertEqual('12345', cast("'12345"))
            self.assertEqual('12345', cast("12345'"))
            self.assertEqual('12345', cast("'12345'"))

    if __name__ == '__main__':
        from test import test_support
        test_support.run_unittest( TestCast )

# vim: ai et sw=4 ts=4
